package com.google.code

import java.io.File
import scala.io.Source
import scala.collection.parallel.mutable
import scala.collection

object XmlParser {
  class MalformedXMLException() extends Exception { }

  private def fixParents(node: XmlNode): XmlNode = node match {
    case c: Comment => c
    case n: Node =>
      n.children match {
        case Nil => n
        case xs =>
          Node(n.nodeName,n.attributes, n.parent,
            xs map (c => fixParents(c)) map (_.changeParent(Some(n))))
      }

  }

  def parse(file: File): Option[XmlDocument] = {
    val ifile = Source.fromFile(file).getLines().toList
    val firstLine = ifile.headOption getOrElse (throw new MalformedXMLException)
    val headpattern = "<\\?xml version=\\\"(\\d.\\d)+\" encoding=\\\"(.+?)\\\"\\?>".r
    val matches = headpattern.findAllIn(firstLine).matchData.toList

    if(matches.length > 1) throw new MalformedXMLException

    val matchgroups = matches.toList.headOption getOrElse (throw new MalformedXMLException)


    val head = Head(matchgroups.group(1), matchgroups.group(2))

    val nodeStack = ifile.tail.foldLeft(new collection.mutable.Stack[XmlNode]())({(stack, line) =>
      val (node, EndNode(isEndnode)) = parseNode(line)
      node match {
        case Some(c: Comment) =>
          val top = stack.pop()
          top match {
            case n: Node =>
              stack.push(n.addChild(c))
            case _: Comment => throw new MalformedXMLException
          }
          stack
        case Some(n: Node) =>
          if(isEndnode) {
            if(stack.isEmpty) throw new MalformedXMLException
            val top = stack.pop()
            if(!stack.isEmpty) {
              val parent = stack.pop()
              parent match {
                case p: Node =>
                  stack.push(p.addChild(top))
                case _: Comment =>
                  throw new MalformedXMLException
              }

            }
            else {
              stack.push(top)
            }
            stack
          }
          else {
            stack.push(n)
          }
        case None =>
          throw new MalformedXMLException
      }
    })

    if(nodeStack.isEmpty || nodeStack.length > 1)
      throw new MalformedXMLException

    val entry = nodeStack.pop()

    Some(XmlDocument(Some(fixParents(entry)), Some(head), file))

  }

  case class EndNode(yes: Boolean)
  private def parseNode(l: String): (Option[XmlNode], EndNode) = {
    val line = l.trim
    val endOfNodep = "</(.+?)>".r
    val singleNodep = "<(.+?)>".r
    val attributeNodep = "<(.+?) (.+?)>".r
    val singleAttributep ="\\s*(.+?)=\\\"(.+?)\\\"\\s*".r
    val commentPattern = "<!--(.+?)-->".r


    line match {
      case endOfNodep(name) => (Some(Node(name, Nil, None, Nil)), EndNode(true))
      case commentPattern(body) => (Some(Comment(body, None)), EndNode(false))
      case attributeNodep(name, attributes) =>
        val atts = singleAttributep.findAllIn(attributes).matchData.map(m => Attribute(m.group(1), m.group(2))).toList

        if(atts.length == 0) (None, EndNode(false))
        else (Some(Node(name, atts, None, Nil)), EndNode(false))
      case singleNodep(name) => (Some(Node(name, Nil, None, Nil)), EndNode(false))
      case _ => (None, EndNode(false))
    }

  }

}
